# coding=utf-8

import io
import json
import traceback
from datetime import date, datetime

import demjson
import os
import re
from copy import deepcopy
from flask import logging
from jinja2 import Environment, FileSystemLoader, PackageLoader
from pexpect import run, ExceptionPexpect

logger = logging.getLogger(__name__)


class JsonEncoder:
    def __init__(self):
        pass


class ComplexEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return obj.strftime('%Y-%m-%d %H:%M:%S')
        elif isinstance(obj, date):
            return obj.strftime('%Y-%m-%d')
        else:
            return json.JSONEncoder.default(self, obj)


def recursive_json_loads(data):
    if isinstance(data, list):
        return [recursive_json_loads(i) for i in data]
    elif isinstance(data, tuple):
        return tuple([recursive_json_loads(i) for i in data])
    elif isinstance(data, dict):
        return Storage(
            {recursive_json_loads(k): recursive_json_loads(data[k]) for k in
             data.keys()})
    else:
        try:
            obj = json.loads(data)
            if obj == data:
                return data
        except:
            return data
        return recursive_json_loads(obj)


class Storage(dict):
    """
    A Storage object is like a dictionary except `obj.foo` can be used
    in addition to `obj['foo']`.
        >>> o = storage(a=1)
        >>> o.a
        1
        >>> o['a']
        1
        >>> o.a = 2
        >>> o['a']
        2
        >>> del o.a
        >>> o.a
        Traceback (most recent call last):
            ...
        AttributeError: 'a'
    """

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError as k:
            raise AttributeError(k)

    def __setattr__(self, key, value):
        self[key] = value

    def __delattr__(self, key):
        try:
            del self[key]
        except KeyError as k:
            raise AttributeError(k)

    def __repr__(self):
        return '<Storage ' + dict.__repr__(self) + '>'


class JsonFileUtil:
    @staticmethod
    def load_file(path):
        with open(path) as json_file:
            data = json.load(json_file)
            return data

    @staticmethod
    def load(json_string):
        return json.loads(json_string)

    @staticmethod
    def match_and_decode(path, match, replace):
        with io.open(path, 'r', encoding="utf8") as _f:
            _config_content = _f.read()

            result = re.sub(match, replace,
                            _config_content)

            return demjson.decode(result)

    @staticmethod
    def dump(json_obj):
        return json.dumps(json_obj, indent=2, sort_keys=False,
                          ensure_ascii=False)

    @staticmethod
    def is_not_json(data):
        try:
            json.loads(data)
        except BaseException as e:
            return True
        return False


class FileUtil:
    @staticmethod
    def file_exists(path):
        return os.path.isfile(path)

    @staticmethod
    def path_exists(path):
        return os.path.isdir(path)

    @staticmethod
    def read(path):
        with io.open(path, 'r', encoding="utf8") as _f:
            return _f.read()

    @staticmethod
    def create_path(path):
        if FileUtil.path_exists(path):
            pass
        SysUtils.run('/bin/bash -c "mkdir -p %s"' % path)

    @staticmethod
    def create_file(path):

        if not FileUtil.file_exists(path):
            parent_file_path = os.path.abspath(
                os.path.join(os.path.dirname(path)))

            FileUtil.create_path(parent_file_path)

        if FileUtil.file_exists(path):
            return
        SysUtils.run('/bin/bash -c "touch  %s"' % path)

    @staticmethod
    def replace_content(file_path, match, replace):
        _config_path = file_path

        if not os.path.isfile(file_path):
            return

        with io.open(_config_path, 'r', encoding="utf8") as _f:
            _config_content = _f.read()

            _config_content = re.sub(match, replace,
                                     _config_content)

            print _config_content

            with io.open(_config_path, 'w', encoding="utf8") as _f:
                _f.write(_config_content)

    @staticmethod
    def match_content(file_path, match):

        if not os.path.isfile(file_path):
            return False

        with io.open(file_path, 'r', encoding="utf8") as _f:
            _config_content = _f.read()

            return re.search(match,
                             _config_content,
                             re.M) is not None

    @classmethod
    def write(cls, file_path, param):

        if not FileUtil.file_exists(file_path):
            FileUtil.create_file(file_path)

        with open(file_path, 'w') as _f:
            _f.write(param)


class ReUtils:
    @staticmethod
    def is_validate_path(path):
        return re.match(r'^[0-9a-zA-Z/\-_]+$', path) is not None


class SysUtils:
    @staticmethod
    def run(command, withexitstatus=1, cwd=None, raise_on_error=False,
            timeout=3000, logfile=None):

        logger.info('Run command:%s' % command)

        msg = ''
        status = 0
        try:
            msg, status = run(command, withexitstatus=withexitstatus, cwd=cwd,
                              timeout=timeout, logfile=logfile)
        except ExceptionPexpect, e:
            if raise_on_error:
                logger.info('[WARN]执行可能不正确, 返回结果Result:%s' % e.message)
                raise e
            else:
                logger.warn(traceback.format_exc())
        finally:
            logger.info('Result:%s' % msg)

        if raise_on_error and not status == 0:
            logger.info('[WARN]执行可能不正确, 返回结果Result:%s' % msg)
            # raise BaseException(msg)

        logger.info('Result:%s' % msg)

        return msg, status


env = Environment(
    loader=PackageLoader("app.modules", "jinja2"))


class Jinja2Utils:
    def __init__(self):
        pass

    @staticmethod
    def render(template, data):
        # type: (object, object) -> object
        output = env.get_template(template).render(data)
        return output.encode('utf-8')


class PageUtils:
    def __init__(self):
        pass

    @staticmethod
    def get_route_id(path, page_file_name):
        # type: (object, object) -> object
        return "%s/%s" % (path, page_file_name)


class DictUtils:
    def __init__(self):
        pass
        
    @staticmethod
    def sample_merge(a,b):
        d = {}
        d.update(a)
        d.update(b)
        return d

    @staticmethod
    def dep_merge(a, b):

        result = deepcopy(a)

        for j in b:
            if j not in result:
                result[j] = b[j]

        for i in result:
            for j in b:
                if i != j:
                    continue
                for item in b[j]:
                    if item in result[i]:
                        continue
                    result[i].append(item)
        return result
